package it.tref.eclipse.wicket.plugin.editors;

import it.tref.eclipse.wicket.plugin.HTMLWicketParser;
import it.tref.eclipse.wicket.plugin.JavaWicketParser;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertiesFileEditor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.wst.sse.ui.StructuredTextEditor;

@SuppressWarnings("restriction")
/**
 * Editor class for multi-page wicket editor
 * 
 * It is composed of 3 editor, java, html and properties.
 * 
 * Next release will implement stats view.
 */
public class WicketFunEditor extends MultiPageEditorPart implements IResourceChangeListener
{
	/**
	 * The java editor is a {@link CompilationUnitEditor}, is internal but it's a bug, 
	 * future release will change the package
	 */
	private JavaEditor javaEditor;
	private IFile javafile;
	
	/**
	 * The html editor and the CSS editor
	 */
	private StructuredTextEditor htmlEditor, cssEditor;
	
	/**
	 * A simple TextEditor for properties
	 */
	private List<PropertiesFileEditor> propertiesEditors;
	private static final String PROPERTIES_REGEX = "(%1$s_\\D{2}_\\D{2}|%1$s)(.properties)";
	
	private int htmlEditorIndex, reportEditorIndex = -1, cssEditorIndex;
	private List<Integer> propertiesEditorIndexs;
	
	
	public WicketFunEditor()
	{
		super();
		
		// the editor will audit changes in workspace
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}
	
	/**
	 * The <code>MultiPageEditorExample</code> implementation of this method
	 * checks that the input is an instance of <code>IFileEditorInput</code>.
	 */
	public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException
	{
		if (!(editorInput instanceof IFileEditorInput))
			throw new PartInitException("Invalid Input: Must be a Wicket Java file");
		super.init(site, editorInput);
	}
	
	/**
	 * Create the java editor
	 */
	private void createJavaEditor()
	{
		try
		{
			javaEditor = new CompilationUnitEditor();
			javaEditor.init(getEditorSite(), getEditorInput());
			
			WicketFunMarkOccurrenceInstaller wfmoi = new WicketFunMarkOccurrenceInstaller(javaEditor);
			wfmoi.installOccurrencesFinder(true);
			
			javafile = ((IFileEditorInput)getEditorInput()).getFile();
			
			setPartName(javafile.getName().substring(0, javafile.getName().length() - javafile.getFileExtension().length() - 1));
			
			int index = addPage(javaEditor, getEditorInput());
			setPageText(index, javaEditor.getTitle());
		} catch (PartInitException e) {
			ErrorDialog.openError(getSite().getShell(), "Error creating Java editor", null, e.getStatus());
		}
	}
	
	/**
	 * Create the html editor
	 */
	private void createHtmlEditor()
	{
		IPath filepath = getFilePath("html");
		
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		if(root.exists(filepath))
		{
			try
			{
				FileEditorInput htmlEditorInput = new FileEditorInput(root.getFile(filepath));
				htmlEditor = new StructuredTextEditor();
				
				htmlEditorIndex = addPage(htmlEditor, htmlEditorInput);
				setPageText(htmlEditorIndex, htmlEditor.getTitle());
			} catch (PartInitException e) {
				ErrorDialog.openError(getSite().getShell(), "Error creating Html editor", null, e.getStatus());
			}
		}
	}
	
	/**
	 * Create the html editor
	 */
	private void createCssEditor()
	{
		IPath filepath = getFilePath("css");
		
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		if(root.exists(filepath))
		{
			try
			{
				FileEditorInput cssEditorInput = new FileEditorInput(root.getFile(filepath));
				cssEditor = new StructuredTextEditor();
				
				cssEditorIndex = addPage(cssEditor, cssEditorInput);
				setPageText(cssEditorIndex, cssEditor.getTitle());
			} catch (PartInitException e) {
				ErrorDialog.openError(getSite().getShell(), "Error creating CSS editor", null, e.getStatus());
			}
		}
	}
	
	
	/**
	 * Create the properties editor
	 */
	private void createPropertiesEditors()
	{
		String propertiesRegEx = String.format(PROPERTIES_REGEX, getFileNameWithoutExtension(javafile));
		
		propertiesEditors = new ArrayList<PropertiesFileEditor>();
		propertiesEditorIndexs = new ArrayList<Integer>();
		
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IFolder folder = root.getFolder(javafile.getFullPath().removeLastSegments(1));
		IResource[] members;
		try {
			members = folder.members();
		} catch (CoreException e1) {
			System.err.print("Folder members error");
			return;
		}
		
		for(IResource res : members)
		{
			if(res.getName().matches(propertiesRegEx))
			{
				PropertiesFileEditor propertiesEditor = new PropertiesFileEditor();
				
				int propertiesEditorIndex;
				try {
					propertiesEditorIndex = addPage(propertiesEditor, new FileEditorInput(((IFile)res)));
				} catch (PartInitException e) {
					ErrorDialog.openError(getSite().getShell(), "Error creating Properties editor", null, e.getStatus());
					continue;
				}
				
				propertiesEditors.add(propertiesEditor);
				propertiesEditorIndexs.add(propertiesEditorIndex);
				
				setPageText(propertiesEditorIndex, propertiesEditor.getTitle());
			}
		}
	}
	
	/**
	 * Create the stats editor
	 */
	private void createReportEditor()
	{
		Composite composite = new Composite(getContainer(), SWT.NONE);
		composite.setBackground(getSite().getShell().getDisplay().getSystemColor(SWT.COLOR_WHITE));
		
		GridLayout layout = new GridLayout();
		composite.setLayout(layout);
		layout.numColumns = 1;
		layout.marginTop = 5;
		
		Label title = new Label(composite, SWT.LEFT);
		title.setText("Wicket id stats");
		title.setLayoutData(new GridData(GridData.BEGINNING));
		
		final Table myTable = new Table(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION);
		myTable.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));
		myTable.setHeaderVisible(true);
		
		TableColumn col1 = new TableColumn(myTable, SWT.NONE);
		col1.setText("Wicket ids");
		
		TableColumn col2 = new TableColumn(myTable, SWT.CENTER);
		col2.setText("Found on java code");
		
		StringBuilder java = JavaWicketParser.read(getEditorFile(getJavaEditor().getEditorInput()).getLocation().toOSString());
		String javafile = java.toString();
		
		if(htmlEditor != null)
		{
			for(String id : HTMLWicketParser.parseWicketIds(getEditorFile(getHtmlEditor().getEditorInput()).getLocation().toOSString()))
			{
				TableItem tableItem1 = new TableItem(myTable, SWT.NULL);
				tableItem1.setText(new String[] {id, javafile.contains("\"" + id + "\"") ? "yes" : "no"});
			}
		}
		
		col1.pack();
		col2.pack();
		
		reportEditorIndex = addPage(composite);
		setPageText(reportEditorIndex, "stats");
	}
	
	public IEditorPart getActiveEditor()
	{
		return super.getActiveEditor();
	}
	
	public PropertiesFileEditor getNextPropertiesEditor()
	{
		if(getActiveEditor() instanceof PropertiesFileEditor)
		{
			int i = propertiesEditors.indexOf(getActiveEditor());
			if(i+1 < propertiesEditors.size())
				return propertiesEditors.get(i+1);
			else
				return propertiesEditors.get(0);
		}
		else if(propertiesEditors.size()>0)
		{
			return propertiesEditors.get(0);
		}
		else
		{
			return null;
		}
	}
	
	private void removeHtmlEditor()
	{
		if(htmlEditor != null)
		{
			removePage(htmlEditorIndex);
			htmlEditor = null;
		}
	}
	
	private void removeCssEditor()
	{
		if(cssEditor != null)
		{
			removePage(cssEditorIndex);
			cssEditor = null;
		}
	}
	
	private void removePropertiesEditors()
	{
		for(int pageIndex : propertiesEditorIndexs)
		{
			removePage(pageIndex);
		}
		
		propertiesEditors.clear();
		propertiesEditorIndexs.clear();
	}
	
	private void removePropertiesEditor(IPath filePath)
	{
		int removeIndex = -1;
		for(int i = 0; i<propertiesEditorIndexs.size(); i++)
		{
			if(((IFileEditorInput)propertiesEditors.get(i).getEditorInput()).getFile().getFullPath().equals(filePath))
			{
				removeIndex = i;
				break;
			}
		}
		if(removeIndex > -1)
		{
			removePage(propertiesEditorIndexs.get(removeIndex));
			propertiesEditorIndexs.remove(removeIndex);
			propertiesEditors.remove(removeIndex);
		}
	}
	
	private void removeReportEditor()
	{
		if(reportEditorIndex > -1)
		{
			removePage(reportEditorIndex);
			reportEditorIndex = -1;
		}
	}
	
	public void setHtmlSelection(String wicketId)
	{
		final IDocument document = htmlEditor.getDocumentProvider().getDocument(htmlEditor.getEditorInput());

		final FindReplaceDocumentAdapter frda = new FindReplaceDocumentAdapter(document);
		
		String[] ids = wicketId.split("/");
		
		try
		{
			IRegion region = null;
			int index = 0;
			
			for(int i=0; i<ids.length; i++)
			{
				region = frda.find(index, '"' + ids[i] + '"', true, true, false, false);
				if(region != null)
				{
					index = region.getOffset();
				}
			}

			if(region != null)
			{
				htmlEditor.selectAndReveal(region.getOffset()+1, region.getLength()-2);
			}
			
			setActiveEditor(htmlEditor);
			htmlEditor.setFocus();
			
		} catch (BadLocationException e) {
			htmlEditor.resetHighlightRange();
		}
	}
	
	private String getFileNameWithoutExtension(IFile file)
	{
		return file.getName().substring(0, file.getName().length() - file.getFileExtension().length() - 1);
	}
	
	/**
	 * Get the path of the editor file by the give extension (java, html, properties)
	 * @param extension
	 * @return
	 */
	private IPath getFilePath(String extension)
	{
		IPath filepath = javafile.getFullPath().removeFileExtension();
		return filepath.addFileExtension(extension);
	}
	
	private IPath getPropertiesNewFilePath(String javaOriginalName, IFile propertiesFile)
	{
		String propertiesName = getFileNameWithoutExtension(propertiesFile);
		
		IPath filepath;
		
		if(propertiesName.length() > javaOriginalName.length())
		{
			String locale = propertiesName.substring(javaOriginalName.length());
			filepath = new Path(javafile.getFullPath().removeFileExtension().toString() + locale);	
		}
		else
		{
			filepath = javafile.getFullPath().removeFileExtension();
		}
		
		return filepath.addFileExtension("properties");
	}
	
	private boolean isPropertiesFile(String name)
	{
		String regex = String.format(PROPERTIES_REGEX, getFileNameWithoutExtension(javafile));
		return name.matches(regex);
	}
	
	/**
	 * Get the resource file wrapped in the given input
	 * @param editorInput
	 * @return
	 */
	private IFile getEditorFile(IEditorInput editorInput)
	{
		return ((IFileEditorInput)editorInput).getFile();
	}
	
	/**
	 * Get the java editor
	 * @return
	 */
	public IEditorPart getJavaEditor() {
		return javaEditor;
	}
	
	/**
	 * Get the html editor
	 * @return
	 */
	public StructuredTextEditor getHtmlEditor() {
		return htmlEditor;
	}
	
	/**
	 * Get the properties editor
	 * @return
	 */
	public PropertiesFileEditor[] getPropertiesEditors() {
		return propertiesEditors.toArray(new PropertiesFileEditor[0]);
	}

	/**
	 * Creates the pages of the multi-page editor.
	 */
	@Override
	protected void createPages()
	{
		createJavaEditor();
		createHtmlEditor();
		createCssEditor();
		createPropertiesEditors();
		if(getHtmlEditor() != null)
			createReportEditor();
	}
	
	/**
	 * Update the input of the derived editor, html and properties
	 */
	private void updateEditors()
	{
		if(htmlEditor != null)
		{
			htmlEditor.setInput(new FileEditorInput(ResourcesPlugin.getWorkspace().getRoot().getFile(getFilePath("html"))));
			setPageText(htmlEditorIndex, htmlEditor.getTitle());
		}
		
		if(cssEditor != null)
		{
			htmlEditor.setInput(new FileEditorInput(ResourcesPlugin.getWorkspace().getRoot().getFile(getFilePath("css"))));
			setPageText(cssEditorIndex, cssEditor.getTitle());
		}
		
		removePropertiesEditors();
		createPropertiesEditors();
	}
	
	/**
	 * The <code>MultiPageEditorPart</code> implementation of this 
	 * <code>IWorkbenchPart</code> method disposes all nested editors.
	 * Subclasses may extend.
	 */
	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		super.dispose();
	}
	
	/**
	 * Saves the multi-page editor's document.
	 */
	public void doSave(IProgressMonitor monitor)
	{
		if(javaEditor != null) javaEditor.doSave(monitor);
		if(htmlEditor != null) htmlEditor.doSave(monitor);
		if(cssEditor != null) cssEditor.doSave(monitor);
		for(TextEditor ed : propertiesEditors)
			ed.doSave(monitor);
		
		removeReportEditor();
		createReportEditor();
	}
	
	/**
	 * Saves the multi-page editor's document as another file.
	 * Also updates the text for page 0's tab, and updates this multi-page editor's input
	 * to correspond to the nested editor's.
	 */
	public void doSaveAs()
	{
		javaEditor.doSaveAs();
		setPageText(0, javaEditor.getTitle());
		setInput(javaEditor.getEditorInput());
		
		String javaOriginalName = javafile.getName().substring(0, javafile.getName().length() - javafile.getFileExtension().length() - 1);
		
		javafile = ((IFileEditorInput)javaEditor.getEditorInput()).getFile();
		
		try {
			if(htmlEditor != null)
				getEditorFile(htmlEditor.getEditorInput()).copy(getFilePath("html"), true, null);
			for(TextEditor ed : propertiesEditors)
				getEditorFile(ed.getEditorInput()).copy(getPropertiesNewFilePath(javaOriginalName, getEditorFile(ed.getEditorInput())), true, null);
			if(cssEditor != null)
				getEditorFile(cssEditor.getEditorInput()).copy(getFilePath("css"), true, null);
				
		} catch (CoreException e) {
			ErrorDialog.openError(getSite().getShell(), "Error saving html/properties file", null, e.getStatus());
			e.printStackTrace();
		}
		
		updateEditors();
	}
	
	public void gotoMarker(IMarker marker) {
		setActiveEditor(javaEditor);
		IDE.gotoMarker(javaEditor, marker);
	}
	
	public boolean isSaveAsAllowed() {
		return true;
	}
	
	/**
	 * If necessary implements what to do if page change
	 */
	protected void pageChange(int newPageIndex) {
		super.pageChange(newPageIndex);
	}
	
	/**
	 * Audit workspace changes
	 */
	public void resourceChanged(final IResourceChangeEvent event)
	{
		if(event.getType() == IResourceChangeEvent.PRE_CLOSE)
		{
			// close all editors
			
			Display.getDefault().asyncExec(new Runnable()
			{
				public void run()
				{
					IWorkbenchPage[] pages = getSite().getWorkbenchWindow().getPages();
					for (int i = 0; i<pages.length; i++)
					{
						if(((FileEditorInput)javaEditor.getEditorInput()).getFile().getProject().equals(event.getResource()))
						{
							IEditorPart editorPart = pages[i].findEditor(javaEditor.getEditorInput());
							pages[i].closeEditor(editorPart, true);
						}
					}
				}            
			});
		}
		else if(event.getType() == IResourceChangeEvent.POST_CHANGE)
		{
			try {
				event.getDelta().accept(new IResourceDeltaVisitor()
				{
					@Override
					public boolean visit(final IResourceDelta delta) throws CoreException
					{
						if(delta.getKind() == IResourceDelta.REMOVED)
						{
							// if file removed, search and close the editor
							
							if(delta.getResource().getFullPath().equals(getFilePath("java"))) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										IEditorPart editorPart = getSite().getPage().findEditor(getEditorInput());
										getSite().getPage().closeEditor(editorPart, true);
									}
								});
							}
							else if(delta.getResource().getFullPath().equals(getFilePath("html"))) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removeHtmlEditor();
									}
								});
							}
							else if(delta.getResource().getFullPath().equals(getFilePath("css"))) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removeCssEditor();
									}
								});
							}
							else if(isPropertiesFile(delta.getResource().getName())) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removePropertiesEditor(delta.getResource().getFullPath());
									}
								});
							}
						}
						else if(delta.getKind() == IResourceDelta.ADDED)
						{
							// on adding file open the new editor if necessary
							
							if(delta.getResource().getFullPath().equals(getFilePath("html"))) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removeHtmlEditor();
										createHtmlEditor();
									}
								});
							}
							if(delta.getResource().getFullPath().equals(getFilePath("css"))) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removeCssEditor();
										createCssEditor();
									}
								});
							}
							else if(isPropertiesFile(delta.getResource().getName())) 
							{
								Display.getDefault().asyncExec(new Runnable()
								{
									@Override
									public void run() {
										removePropertiesEditors();
										createPropertiesEditors();
									}
								});
							}
						}
						return true;
					}
				});
			} catch (CoreException e) {
				e.printStackTrace();
			}
		}
	}
}